﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;

namespace YouTubeStormer
{
    public partial class Main : Form
    {
        Configuration configuration;
        Communications communications;
        CommentVoting commentVoting;
        CommentPosting commentPosting;
        LoginRelated loginRelated;

        BackgroundWorker stormWorker;
        Timer cycleTimer;

        int nextCycleTick = 0;
        int commentsOnCurrentAccount = 0;
        int votesOnCurrentAccount = 0;
        string videoLink;
        DebugDataHelper debugData;

        public Main()
        {
            InitializeComponent();
            Icon = Properties.Resources.storm;
            CheckForIllegalCrossThreadCalls = false;
            communications = new Communications();
            commentVoting = new CommentVoting(communications);
            commentPosting = new CommentPosting(communications);
            loginRelated = new LoginRelated(communications);
            
            configuration = new Configuration();
            configuration.VisibleChanged += configuration_VisibleChanged;
            splitContainerMain.Panel2Collapsed = !Properties.Settings.Default.ShowDebugInfo;

            stormWorker = new BackgroundWorker();
            stormWorker.DoWork += stormWorker_DoWork;
            stormWorker.RunWorkerCompleted += stormWorker_RunWorkerCompleted;
            stormWorker.WorkerSupportsCancellation = true;

            cycleTimer = new Timer();
            cycleTimer.Tick += cycleTimer_Tick;
            cycleTimer.Interval = 1000;
            cycleTimer.Enabled = true;
        }

        void cycleTimer_Tick(object sender, EventArgs e)
        {
            if (nextCycleTick >= Properties.Settings.Default.TimerInterval * 60)
            {
                stats.Statistics.NextCycle = 0;
                nextCycleTick = 0;
                cycleTimer.Stop();
                stormWorker.RunWorkerAsync();
            }
            else
            {
                nextCycleTick++;
                stats.Statistics.NextCycle = (Properties.Settings.Default.TimerInterval * 60) - nextCycleTick;
            }
        }

        void stormWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            buttonStorm.Enabled = true;
            buttonStorm.Text = buttonStormCancel;
            Storm();
        }

        void stormWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            cycleTimer.Start();
            buttonStorm.Text = buttonStormStart;
            buttonStorm.Enabled = true;
            richTextBoxCookieDebug.Clear();
            richTextBoxInputDebug.Clear();
            richTextBoxOutputDebug.Clear();
        }

        #region Main Methods

        void Storm()
        {
            log.PerformLog("# Beginning Cycle " + (stats.Statistics.Cycles + 1) + " - " + DateTime.Now.ToShortTimeString() + " #");
            var cycleTimeSpan = DateTime.Now;
            int candySeen = 0;

            foreach (string videoID in Properties.Settings.Default.VideoIDs)
            {
                if (stormWorker.CancellationPending) return;

                if (loginRelated.CurrentlyLoggedIn.Username == null)
                {
                    if (!PerformRandomLogin()) break;
                }

                videoLink = Communications.YoutubeWatchUrl + videoID;

                var comment = commentPosting.FindCommentOnPage(videoID);

                if (comment != null && !comment.Spammed)
                {
                    if (comment.HighestRated) candySeen++;

                    var accounts = loginRelated.FindAvailableAccountsForVote(comment.ID);
                    if (accounts != null)
                    {
                        int votesOnVideo = 0;
                        foreach (LoginRelated.Account account in accounts)
                        {
                            if (PerformLogin(account))
                            {
                                if (PerformVote(videoID, comment))
                                {
                                    votesOnVideo++;
                                    if (votesOnVideo >= Properties.Settings.Default.VotesPerVideo) goto Complete;
                                }
                            }
                        }
                    }

                    if (!comment.HighestRated)
                    {
                        log.PerformLog("No accounts available to vote comment on {LINK} (going to perform comment)", linkText: videoID, link: videoLink);
                        PerformComment(videoID);

                    }
                    else
                    {
                        log.PerformLog("No accounts available to vote comment on {LINK} (skipped because already highest comment)", linkText: videoID, link: videoLink);
                    }
                }

                else
                {
                    if (comment != null && comment.Spammed)
                    {
                        stats.Statistics.SpammedComments++;
                        log.PerformLog("Comment was marked as spam on {LINK}, performing new comment..", linkText: videoID, link: videoLink);
                    }
                    PerformComment(videoID);
                }

            Complete:
                stats.Statistics.CandyRatio = new KeyValuePair<int, int>(candySeen, Properties.Settings.Default.VideoIDs.Count);
                continue;

            }

            stats.Statistics.Cycles++;
            int cycleDuration = (int)DateTime.Now.Subtract(cycleTimeSpan).TotalSeconds;
            stats.Statistics.CycleDurations.Add(cycleDuration);
            
            log.PerformLog("# Finished Cycle " + (stats.Statistics.Cycles + 1) + " in " + cycleDuration + " Seconds #");
            stats.SendStats(log.Store);
            log.PerformLog("", EmptyStore: true);

        }

        bool PerformLogin(LoginRelated.Account account)
        {
            if (loginRelated.CurrentlyLoggedIn.Username == account.Username) return true;

            commentsOnCurrentAccount = 0;

            bool loginSuccess = loginRelated.PerformLogin(out debugData, account);
            if (loginSuccess)
            {
                log.PerformLog("Logged into " + account.Username);
                stats.Statistics.Logins++;
            }
            else
            {
                if (debugData.Output == "proxyfail")
                {
                    log.PerformLog("Proxy failed for " + account.Username);
                }
                else
                {
                    log.PerformLog("Failed to login to " + account.Username);
                    stats.Statistics.LoginErrors++;
                }
            }

            //Mundane
            UpdateDebugData(debugData);
            //---

            return loginSuccess;
        }

        bool PerformRandomLogin()
        {
            foreach (string account in Configuration.StringCollectionToArray(Properties.Settings.Default.Accounts))
            {
                var accountParsed = loginRelated.ParseAccount(account);
                if (accountParsed.Username != loginRelated.CurrentlyLoggedIn.Username && PerformLogin(accountParsed))
                    return true;
            }

            log.PerformLog("Unable to login to any accounts");
            return false;

        }


        bool PerformComment(string videoID)
        {
            if (commentsOnCurrentAccount >= Properties.Settings.Default.CommentsPerAccount)
                if (!PerformRandomLogin()) 
                    return false;

            foreach (string account in Properties.Settings.Default.Accounts)
            {
                if (PerformLogin(loginRelated.ParseAccount(account)))
                {
                    string commentID;
                    if (commentPosting.PerformComment(out debugData, out commentID, CommentPosting.GenerateComment(), videoID) == CommentPosting.Result.Commented)
                    {
                        UpdateDebugData(debugData);
                        loginRelated.LogAccountVote(commentID, loginRelated.CurrentlyLoggedIn.Username);
                        log.PerformLog("Commented {LINK}", linkText: videoID, link: videoLink);
                        commentsOnCurrentAccount++;
                        stats.Statistics.Posted++;
                        return true;
                    }
                    else
                    {
                        UpdateDebugData(debugData);
                        log.PerformLog("Commenting failed on {LINK}", linkText: videoID, link: videoLink);
                        stats.Statistics.PostingErrors++;
                    }
                }
            }
            return false;

        }

        bool PerformVote(string videoID, CommentDetails.CommentDetail comment)
        {
            var voteResult = commentVoting.PerformVote(out debugData, videoID, comment);
            UpdateDebugData(debugData);
            switch (voteResult)
            {
                case CommentVoting.Result.Voted:
                    loginRelated.LogAccountVote(comment.ID, loginRelated.CurrentlyLoggedIn.Username);
                    log.PerformLog("Voted comment on {LINK} with " + loginRelated.CurrentlyLoggedIn.Username + (comment.HighestRated ? " *highest rated*" : ""), linkText: videoID, link: videoLink);
                    stats.Statistics.Votes++;
                    return true;
                case CommentVoting.Result.Errored:
                    log.PerformLog("Error Voting comment on {LINK} with " + loginRelated.CurrentlyLoggedIn.Username, linkText: videoID, link: videoLink);
                    stats.Statistics.VoteErrors++;
                    return false;
                case CommentVoting.Result.AlreadyVoted:
                    log.PerformLog("Already Voted Glitch on {LINK} with " + loginRelated.CurrentlyLoggedIn.Username, linkText: videoID, link: videoLink);
                    stats.Statistics.AlreadyVotedGlitches++;
                    return false;
            }
            return false;
        }

        #endregion

        delegate void UpdateDebugDataDelegate(DebugDataHelper data);
        public void UpdateDebugData(DebugDataHelper data)
        {
            if (richTextBoxInputDebug.InvokeRequired || richTextBoxOutputDebug.InvokeRequired) { Invoke(new UpdateDebugDataDelegate(UpdateDebugData), data); }
            else
            {
                richTextBoxInputDebug.Text = data.Input;
                richTextBoxOutputDebug.Text = data.Output;
                richTextBoxCookieDebug.Text = communications.CookieDebug;
            }
        }

        public class DebugDataHelper
        {
            public string Output { get; set; }
            public string Input { get; set; }
        }

        private void buttonConfigure_Click(object sender, EventArgs e)
        {
            configuration.Show();
        }

        string buttonStormCancel = "Cancel Cycle";
        string buttonStormStart = "Storm";
        private void buttonStorm_Click(object sender, EventArgs e)
        {
            if (buttonStorm.Text == buttonStormStart)
            {
                buttonStorm.Enabled = false;
                nextCycleTick = Properties.Settings.Default.TimerInterval * 60;
            }
            else
            {
                buttonStorm.Enabled = false;
                stormWorker.CancelAsync();
            }
        }

        void configuration_VisibleChanged(object sender, EventArgs e)
        {
            if (!configuration.Visible) splitContainerMain.Panel2Collapsed = !Properties.Settings.Default.ShowDebugInfo;
        }

    }
}